/*
 * Copyright (c) 2009. Silenus Consultoria, S.L.U.
 */
package es.silenus.detecta.client;

import org.apache.commons.configuration.Configuration;
import org.apache.commons.configuration.PropertiesConfiguration;

import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.multipart.FilePart;
import org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity;
import org.apache.commons.httpclient.methods.multipart.Part;
import org.apache.commons.httpclient.methods.multipart.StringPart;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.filefilter.AgeFileFilter;
import org.apache.commons.io.filefilter.AndFileFilter;
import org.apache.commons.io.filefilter.RegexFileFilter;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.codehaus.jettison.json.JSONObject;

import java.io.File;
import java.io.IOException;

import java.net.MalformedURLException;

import java.util.Collection;


/**
 * Upload client.
 *
 * @author <a href="mailto:malonso@silenus-consultoria.es">Mariano Alonso</a>
 *
 * @since 11-ago-2009 12:10:59
 */
public class UploadClient {
	/**
	 * Logger for this class.
	 */
	private static final Log LOG = LogFactory.getLog(UploadClient.class);

	/**
	 * Five minutes in milliseconds.
	 */
	private static final long FIVE_MINUTES_IN_MS = 5 * 60 * 1000;

	/**
	 * Upload success token.
	 */
	private static final String UPLOAD_SUCCESS = "{success: true}";

	/**
	 * The configuration.
	 */
	private Configuration configuration;

	/**
	 * The http client.
	 */
	private HttpClient client;

	/**
	 * The authenticator.
	 */
	private AbstractFormAuthenticator authenticator;

	/**
	 * Sets the configuration.
	 *
	 * @param configuration the configuration.
	 */
	public void setConfiguration(final Configuration configuration) {
		this.configuration = configuration;
	}

	/**
	 * Initializes the HTTP client
	 *
	 * @throws ClassNotFoundException if the authenticator classname does not exist.
	 * @throws IllegalAccessException if the authenticator cannot be constructed.
	 * @throws InstantiationException if the authenticator cannot be constructed.
	 */
	private void initialize() throws ClassNotFoundException, IllegalAccessException, InstantiationException {
		LOG.info("Initializing HTTP client...");

		// Initialize the HTTP client
		client = new HttpClient(new MultiThreadedHttpConnectionManager());

		// Initialize the form based authenticaator
		String className = configuration.getString("form.authenticator");

		authenticator = (AbstractFormAuthenticator)Class.forName(className).newInstance();
		authenticator.setConfiguration(configuration);
		authenticator.setClient(client);
	}

	/**
	 * Get the upload URL.
	 *
	 * @return the upload url.
	 *
	 * @throws MalformedURLException if URL is not correct.
	 */
	private String getUploadURL() throws MalformedURLException {
		return authenticator.getURL(configuration.getString("upload.uri", null));
	}

	/**
	 * Processes a file.
	 *
	 * @param file the file.
	 */
	private void process(final File file) {
		try {
			LOG.info("Processing file  \"" + file.getName() + "\" ...");

			final String uploadURL = getUploadURL();

			PostMethod uploadMethod = null;

			try {
				uploadMethod = new PostMethod(uploadURL);
				uploadMethod.setFollowRedirects(false);


				Part[] parts = { new StringPart("filename", file.getName()), new FilePart("file", file) };

				uploadMethod.setRequestEntity(new MultipartRequestEntity(parts, uploadMethod.getParams()));


				client.executeMethod(uploadMethod);

				if(LOG.isDebugEnabled()) {
					LOG.debug("Upload response: " + authenticator.formatMethodResponse(uploadMethod));
				}

				JSONObject uploadResponse = new JSONObject(uploadMethod.getResponseBodyAsString());
				if(uploadResponse.getBoolean("success")) {
					LOG.info("File: " + file.getAbsolutePath() + " was successfully uploaded and will be removed");

					// Remove file
					if(!file.delete()) {
						file.deleteOnExit();
					}
				} else {
					LOG.error("File: " + file.getAbsolutePath() + " could not be uploaded, check server log for details");
				}
			} finally {
				if(uploadMethod != null) {
					uploadMethod.releaseConnection();
				}
			}
		} catch(Throwable e) {
			LOG.error("Error uploading file: " + file.getName(), e);
		}
	}

	/**
	 * Runs the client.
	 *
	 * @throws IOException if something goes wrong.
	 * @throws ClassNotFoundException if the authenticator classname does not exist.
	 * @throws InstantiationException if the authenticator cannot be constructed.
	 * @throws IllegalAccessException if the authenticator cannot be constructed.
	 */
	@SuppressWarnings({"unchecked"})
	public void run() throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException {
		// Read configuration variables
		final String directoryPath = configuration.getString("omr.directory", ".");
		final long threshold = configuration.getLong("age.threshold", FIVE_MINUTES_IN_MS);
		final String namePattern = configuration.getString("name.pattern", "*.csv");

		LOG.info("Started Upload client at directory: " + directoryPath);

		final long cutoff = System.currentTimeMillis() - threshold;

		// Build directory filter
		final AndFileFilter fileFilter = new AndFileFilter();
		final AgeFileFilter ageFilter = new AgeFileFilter(cutoff, true);
		final RegexFileFilter nameFilter = new RegexFileFilter(namePattern);

		fileFilter.addFileFilter(ageFilter);
		fileFilter.addFileFilter(nameFilter);

		// If the directory exists
		final File directory = new File(directoryPath);

		if(directory.exists() && directory.isDirectory()) {
			final Collection<File> files = FileUtils.listFiles(directory, fileFilter, null);

			if(files.size() > 0) {
				// Initialize http client
				this.initialize();

				// Login
				authenticator.login();

				// Iterate over files
				for(File file : files) {
					this.process(file);
				}

				// Logout
				authenticator.logout();
			} else {
				LOG.info("There are no files to process");
			}
		} else {
			LOG.info("The supplied directory \"" + directoryPath + "\" does not exist or it is not a directory");
		}
	}

	/**
	 * Main program entry point.
	 *
	 * @param args the arguments.
	 */
	public static void main(String[] args) {
		if(args.length != 1) {
			System.out.println("Usage: <program> configuration.properties");
		}

		try {
			Configuration configuration = new PropertiesConfiguration(args[0]);

			UploadClient client = new UploadClient();

			client.setConfiguration(configuration);
			client.run();
		} catch(Throwable e) {
			LOG.error("Error running upload client with configuration: \"" + args[0] + "\"", e);
		}
	}
}
